package com.chenyuxin.ilp.service.file.Impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.chenyuxin.ilp.exception.BaseException;
import com.chenyuxin.ilp.mapper.file.FileFolderMapper;
import com.chenyuxin.ilp.mapper.file.FileMetaMapper;
import com.chenyuxin.ilp.mapper.file.FilePreviewMapper;
import com.chenyuxin.ilp.model.constant.FileExtensionConstant;
import com.chenyuxin.ilp.model.entity.file.FileFolder;
import com.chenyuxin.ilp.model.entity.file.FileMeta;
import com.chenyuxin.ilp.model.entity.file.FilePreview;
import com.chenyuxin.ilp.model.enums.exception.ExceptionEnums;
import com.chenyuxin.ilp.model.enums.exception.FileExceptionEnums;
import com.chenyuxin.ilp.model.pojo.Result;
import com.chenyuxin.ilp.model.vo.file.FileVo;
import com.chenyuxin.ilp.model.vo.file.FolderParentVo;
import com.chenyuxin.ilp.model.vo.file.FolderVo;
import com.chenyuxin.ilp.service.file.OpenFileService;
import com.chenyuxin.ilp.service.file.SharePermissionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class OpenFileServiceImpl implements OpenFileService {
    @Value("${minio.bucket.files}")
    private String bucket;
    @Autowired
    private FileMetaMapper fileMetaMapper;
    @Autowired
    private FileFolderMapper fileFolderMapper;
    @Autowired
    private FilePreviewMapper filePreviewMapper;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private MinioServiceImpl minioService;
    @Autowired
    private SharePermissionService sharePermissionService;

    // 查询用户所有可访问的公开文件及文件夹
    @Override
    public Result selectAllOpenFile(String userId, String hostUserId) {
        // 查询所有文件公开
        LambdaQueryWrapper<FileMeta> fileWrapper = new LambdaQueryWrapper<>();
        fileWrapper.eq(FileMeta::getUserId,hostUserId)
                .in(FileMeta::getIsOpen,Arrays.asList(1,2))
                .eq(FileMeta::getIsDelete,0);
        List<FileMeta> fileMetas = fileMetaMapper.selectList(fileWrapper);
        // 查询所有公开文件夹
        LambdaQueryWrapper<FileFolder> folderWrapper = new LambdaQueryWrapper<>();
        folderWrapper.eq(FileFolder::getUserId,hostUserId)
                .in(FileFolder::getIsOpen,Arrays.asList(1,2));
        List<FileFolder> fileFolders = fileFolderMapper.selectList(folderWrapper);
        List<Object> allOpenFileAndFolder = new ArrayList<>();
        Map<Long, FolderParentVo> folderMap = new HashMap<>();
        // 处理文件夹的嵌套关系
        // 将所有文件夹存入嵌套结构中
        for (FileFolder folder : fileFolders) {
            FolderParentVo folderParentVo = BeanUtil.copyProperties(folder, FolderParentVo.class);
            folderParentVo.setChildren(new ArrayList<>());
            folderParentVo.setType(2);
            folderMap.put(folder.getId(), folderParentVo);
        }
        // 2. 遍历文件夹，找到对应的父节点并将文件夹添加到父节点的children中
        for (FileFolder folder : fileFolders) {
            FolderParentVo folderParentVo = folderMap.get(folder.getId());
            if(!folderMap.containsKey(folderParentVo.getParentId())) {
                allOpenFileAndFolder.add(folderParentVo);
            } else {
                FolderParentVo parentFolderParentVo = folderMap.get(folder.getParentId());
                if (parentFolderParentVo != null) {
                    parentFolderParentVo.getChildren().add(folderParentVo); // 子节点加入到父节点的children中
                }
            }
        }
        // 将匹配的文件填入文件夹嵌套结构中
        for (FileMeta fileMeta : fileMetas) {
            FileVo fileVo = BeanUtil.copyProperties(fileMeta, FileVo.class);
            fileVo.setType(1);
            Integer isParent = 1;
            for (FolderParentVo folderParentVo : folderMap.values()) {
                if (fileVo.getParentId() != null && fileVo.getParentId().equals(folderParentVo.getId())) {
                    // 文件加入文件夹的children中
                    isParent = 0;
                    folderParentVo.getChildren().add(fileVo);
                }
            }
            if(isParent==1){
                allOpenFileAndFolder.add(fileVo);
            }
        }
        Result result = sharePermissionService.selectHostFileTree(hostUserId, userId);
        List<Object> data = (List<Object>)result.getData();
        allOpenFileAndFolder.addAll(data);
        // 修改访问量
        String s = stringRedisTemplate.opsForValue().get("open:preview:total:" + hostUserId);
        if(s==null){
            stringRedisTemplate.opsForValue().set("open:preview:total:"+hostUserId,String.valueOf(1));
        }else{
            stringRedisTemplate.opsForValue().increment("open:preview:total:"+hostUserId);
        }
        return Result.ok(allOpenFileAndFolder);
    }
    // 根据父节点id查询文件夹内容
    @Override
    public Result selectByParentId(Long parentId, String hostUserId, String userId) {
        // 查询子节点文件夹
        LambdaQueryWrapper<FileFolder> folderWrapper = new LambdaQueryWrapper<>();
        folderWrapper.eq(FileFolder::getParentId,parentId)
                .eq(FileFolder::getUserId,hostUserId)
                .in(FileFolder::getIsOpen,Arrays.asList(1,2));
        List<FileFolder> fileFolders = fileFolderMapper.selectList(folderWrapper);
        // 查询子节点文件夹
        LambdaQueryWrapper<FileMeta> fileWrapper = new LambdaQueryWrapper<>();
        fileWrapper.eq(FileMeta::getParentId,parentId)
                .eq(FileMeta::getUserId,hostUserId)
                .eq(FileMeta::getIsDelete,0)
                .in(FileMeta::getIsOpen,Arrays.asList(1,2));
        List<FileMeta> fileMetas = fileMetaMapper.selectList(fileWrapper);
        List<Object> sonFileFolder = new ArrayList<>();
        for(FileFolder folder:fileFolders){
            FolderVo folderVo = BeanUtil.copyProperties(folder, FolderVo.class);
            folderVo.setType(1);
            sonFileFolder.add(folderVo);
        }
        for(FileMeta fileMeta:fileMetas){
            FileVo fileVo = BeanUtil.copyProperties(fileMeta,FileVo.class);
            fileVo.setType(0);
            sonFileFolder.add(fileVo);
        }
        return Result.ok(sonFileFolder);
    }
    // 预览公开文件
    @Override
    public Result previewOpenFile(Long fileId, String hostUserId, String userId) {
        LambdaQueryWrapper<FileMeta> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(FileMeta::getUserId,hostUserId)
                .eq(FileMeta::getId,fileId)
                .eq(FileMeta::getIsDelete,0);
        FileMeta fileMeta = fileMetaMapper.selectOne(wrapper);
        if(fileMeta==null){
            throw new BaseException(FileExceptionEnums.PREVIEW_FILE_NO_EXIST);
        }
        if(!Arrays.asList(1,2).contains(fileMeta.getIsOpen())){
            throw new BaseException(FileExceptionEnums.NOT_PERMISSION_PREVIEW);
        }
        String fileName = fileMeta.getFileName();
        String extension = fileName.substring(fileName.lastIndexOf("."));
        String objectFileName;
        if(FileExtensionConstant.allowedPreview.contains(extension)){
            objectFileName = "/"+hostUserId+"/file/"+fileId+extension;
        } else if (FileExtensionConstant.convertPreview.contains(extension)) {
            LambdaQueryWrapper<FilePreview> previewWrapper = new LambdaQueryWrapper<>();
            previewWrapper.eq(FilePreview::getInitialFileId,fileId);
            FilePreview filePreview = filePreviewMapper.selectOne(previewWrapper);
            objectFileName = "/"+hostUserId+"/preview/"+filePreview.getPreviewFileName();
        }else {
            throw new BaseException(FileExceptionEnums.FILE_CANT_PREVIEW);
        }
        String previewUrl = minioService.getPreviewUrl(bucket,objectFileName);
        // 更新文件预览量
        String s = stringRedisTemplate.opsForValue().get("open:preview:total:" + hostUserId);
        if(s==null){
            stringRedisTemplate.opsForValue().set("open:preview:total:"+hostUserId,String.valueOf(1));
        }else{
            stringRedisTemplate.opsForValue().increment("open:preview:total:"+hostUserId);
        }
        return Result.ok(previewUrl);
    }
    // 下载公开文件
    @Override
    public ResponseEntity<byte[]> downLoadFile(Long fileId, String hostUserId, String userId) {
        LambdaQueryWrapper<FileMeta> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(FileMeta::getId,fileId)
                .eq(FileMeta::getIsDelete,0)
                .eq(FileMeta::getUserId,hostUserId);
        FileMeta fileMeta = fileMetaMapper.selectOne(wrapper);
        if(fileMeta==null) {
            throw new BaseException(FileExceptionEnums.FILE_NOT_EXIST);
        }
        String fileName = fileMeta.getFileName();
        String extension = fileName.substring(fileName.lastIndexOf("."));
        if(!Arrays.asList(2).contains(fileMeta.getIsOpen())){
            throw new BaseException(FileExceptionEnums.NOT_PERMISSION_DOWNLOAD);
        }
        String objectName = "/"+hostUserId + "/file/" + fileId+extension;
        byte[] fileByte = minioService.download(objectName);
        HttpHeaders headers = new HttpHeaders();
        try {
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setContentDispositionFormData("attachment", URLEncoder.encode(fileMeta.getFileName(), StandardCharsets.UTF_8.toString()));
        } catch (UnsupportedEncodingException e) {
            log.error("封装ResponseEntity出现异常,objectName:{},errorMessage:{}",objectName,e.getMessage());
            throw new BaseException(ExceptionEnums.SYSTEM_ERROR);
        }
        String s = stringRedisTemplate.opsForValue().get("open:download:total:" + hostUserId);
        if(s==null){
            stringRedisTemplate.opsForValue().set("open:download:total:"+hostUserId,String.valueOf(1));
        }else{
            stringRedisTemplate.opsForValue().increment("open:download:total:"+hostUserId);
        }
        return new ResponseEntity<>(fileByte,headers, HttpStatus.OK);
    }
}
